package server;
import java.net.MalformedURLException;
import java.rmi.*;

import java.rmi.server.UnicastRemoteObject;
import java.sql.SQLException;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import se.kth.id2212.bankjdbc.Account;
import se.kth.id2212.bankjdbc.Bank;
import se.kth.id2212.bankjdbc.RejectedException;

import client.Trader;
import client.TraderImpl;
import common.InsufficientFundsException;
import common.Item;
import common.ItemList;
import common.ItemListImpl;
import common.LoginDetails;
import common.NameOccupiedException;


public class MarketplaceImpl extends UnicastRemoteObject implements Marketplace  {

	/**
	 * 
	 */
	private static final long serialVersionUID = 7410176714751133635L;
	private Bank bank;
	private String bankName = "NordBanken";
	private Hashtable<String, Trader> traders;
	private DBConnection dbc;
	private ReadWriteLock tradersLock = new ReentrantReadWriteLock(), itemsLock = new ReentrantReadWriteLock(), watchedLock = new ReentrantReadWriteLock();
	public MarketplaceImpl() throws RemoteException, MalformedURLException, NotBoundException, ClassNotFoundException, SQLException {
		super();
		bank = (Bank)Naming.lookup(bankName);
		traders = new Hashtable<String,Trader>();
		String[] details = LoginDetails.getLoginDetails();
		this.dbc = new DBConnection(details[0],"javanetwork",details[1],details[2],(Marketplace)this);
	}
	
	@Override
	public void replaceTrader(Trader trader) throws RemoteException,
			SQLException {
		String name = trader.getTraderName();
		
		if(traders.containsKey(name))
			traders.remove(name);
		
		traders.put(trader.getTraderName(), trader);
		
		
	}
	
	@Override
	public boolean login(String username, String password)
			throws RemoteException, SQLException {
		return dbc.login(username, password);
	}

	@Override
	public void logout(String username) throws RemoteException, SQLException {
		dbc.logout(username);
		
	}
	
	@Override
	public synchronized boolean traderNameAvailable(String traderName) throws RemoteException, SQLException {
		return dbc.usernameAvailable(traderName);
	}

	@Override
	public synchronized void registerTrader(Trader trader) throws NameOccupiedException,
			RemoteException, SQLException {
		System.out.println("Register trader started " + trader.getTraderName());
		Account acc = null;
		String traderName = trader.getTraderName();
		System.out.println("Trying to register trader");
		
		if(!traderNameAvailable(traderName))
			throw new NameOccupiedException(traderName);
		
		//Registering account in db
		dbc.registerUser(traderName, trader.getTraderPassword());
		
		
		try
		{
		acc = bank.newAccount(traderName);
		acc.deposit(1000);
		}  catch (RejectedException e) {
			System.out.println("Bank account already exists, using current account instead");
		}
		tradersLock.writeLock().lock();
		try
		{
		traders.put(traderName, trader);
		} finally
		{
			tradersLock.writeLock().unlock();
		}
		System.out.println("Trader registered " + traderName);
		
	}

	@Override
	public synchronized ItemList getItems() throws RemoteException, SQLException {
		System.out.println("attmpt getItems");
		return new ItemListImpl(dbc.getItemsForSale());
	}

	@Override
	public synchronized boolean buyItem(Item item,String traderName) throws InsufficientFundsException,
			RemoteException, SQLException {
		boolean found = false;
		LinkedList<Item> items = dbc.getItemsForSale();
		Iterator<Item> i = items.iterator();
		try
		{
			while (i.hasNext())
			{
				if (i.next().equals(item))
				{
					float price = item.getPrice();
					bank.getAccount(traderName).withdraw(price);
					bank.getAccount(item.getOwner()).deposit(price);
					System.out.println("Found item, " + item.toString() + " is now sold");
					found = true;
					dbc.removeSoldItem(item);
					
					System.out.println("Item sold, owner " + item.getOwner());
					
					dbc.incrementItemsSold(item.getOwner());
					dbc.incrementItemsBought(traderName);
					
					if (dbc.getLoginStatus(item.getOwner()))
						traders.get(item.getOwner()).itemSold(item);
					break;
				} 	
			}
		} catch (RejectedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if (!found)
		{
			System.out.println("Item not found");
		}
		return found;
	}

	@Override
	public synchronized void sellItem(Item item) throws RemoteException, SQLException {
		System.out.println("New item to sell " + item.getInfoStr());
		dbc.addItemForSale(item);
		matchItemToWatchedItems(item);
	}


	@Override
	public synchronized void watchForItem(String itemName, float maxPrice, String traderName)
			throws RemoteException, SQLException {
		System.out.println("Watching for item");
		tradersLock.readLock().lock();
		Trader trader;
		try {
		trader = traders.get(traderName);
		} finally
		{
			tradersLock.readLock().unlock();
		}
		WatchedItem watchedItem = new WatchedItem(traderName,itemName,maxPrice);
		LinkedList<Item> matches = matchWatchedItem(watchedItem);
		Iterator<Item> i = matches.iterator();
		Item tmp;
		if (!matches.isEmpty())
		{
			while(i.hasNext())
			{
				tmp = i.next();
				System.out.println("Notifying about " + tmp.getInfoStr());
				if (dbc.getLoginStatus(traderName))
					trader.itemAvaliable(tmp);
			}
			
		} else
		{
			dbc.addWatchedItem(watchedItem);
		}
	}

	@Override
	public synchronized float getAccountBalance(String traderName) throws RemoteException {
		 
		float balance = 0;
		try {
			balance = bank.getAccount(traderName).getBalance();
		} catch (RejectedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return balance;
	}

	public synchronized void depositToAccount(String traderName, float balance) throws RemoteException
	{
		try {
			bank.getAccount(traderName).deposit(balance);
		} catch (RejectedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private synchronized LinkedList<Item> matchWatchedItem(WatchedItem watchedItem) throws RemoteException, SQLException
	{
		LinkedList<Item> matches = new LinkedList<Item>();
		Item item;
		LinkedList<Item> items = dbc.getItemsForSale();
		Iterator<Item> i = items.iterator();
		while (i.hasNext())
		{
			item = i.next();
			if (item.getName().equalsIgnoreCase(watchedItem.getItemName()) 
					&& item.getPrice() <= watchedItem.getMaxprice())
			{
				matches.add(item);
				System.out.println("Matched item " + item.getInfoStr());
			}
				
		}

		return matches;
	}
	
	private synchronized void matchItemToWatchedItems(Item item) throws RemoteException, SQLException
	{
		WatchedItem watchedItem;
		LinkedList<WatchedItem> watchedItems = dbc.getWatchedItems();
		Iterator<WatchedItem> i = watchedItems.iterator();
		while(i.hasNext())
		{
			watchedItem = i.next();
			
			if (item.getName().equals(watchedItem.getItemName()) 
					&& item.getPrice() <= watchedItem.getMaxprice())
			{
				if (dbc.getLoginStatus(watchedItem.getTraderName()))
				{
					Trader trader = traders.get(watchedItem.getTraderName());
					trader.itemAvaliable(item);
					System.out.println("Notified " + trader.getTraderName() + " about item " + item.toString());
					dbc.removeWatchedItem(watchedItem);
				}
			}
		}
	
	}

	@Override
	public synchronized int getItemsBought(String name) throws RemoteException, SQLException {
		return dbc.getItemsBought(name);
	}

	@Override
	public synchronized int getItemsSold(String name) throws RemoteException, SQLException {
		return dbc.getItemsSold(name);
	}

	@Override
	public synchronized void shutdownMarketplace() throws RemoteException, SQLException {
		System.out.println("Shutdown method initiated");

		dbc.logoutAllTraders();
		System.exit(0);
	}

}
